package com.ruyuan.process.engine.model;

import com.ruyuan.process.engine.instance.ProcessorInstanceCreator;
import com.ruyuan.process.engine.node.ProcessorDefinition;
import com.ruyuan.process.engine.node.ProcessorNode;
import com.ruyuan.process.engine.process.Processor;
import lombok.Data;

import java.util.HashMap;
import java.util.Map;

/**
 * 表示解析出来的一个流程配置信息
 *
 * @author zhonghuashishan
 * @version 1.0
 */
@Data
public class ProcessModel {
    public String name;
    public Map<String, ProcessNodeModel> nodes = new HashMap<>();

    /**
     * 添加一个节点
     *
     * @param processNodeModel 节点配置
     */
    public void addNode(ProcessNodeModel processNodeModel) {
        if (nodes.containsKey(processNodeModel.getName())) {
            throw new IllegalArgumentException("同一个流程不能定义多个相同id的节点");
        }
        nodes.put(processNodeModel.getName(), processNodeModel);
    }

    /**
     * 检查配置是否合法：
     * 1、className是否合法
     * 2、上下节点依赖是否正确。
     * 3、是否仅有一个开始节点
     */
    public void check() {
        int startNode = 0;
        for (ProcessNodeModel processNodeModel : nodes.values()) {
            String className = processNodeModel.getClassName();
            try {
                Class.forName(className); // 先去判断一下每个流程节点的class是否可以加载，是否是有效的class
            } catch (Throwable e) {
                throw new IllegalArgumentException("无法加载节点[" + processNodeModel.getName() + "]的类：" + className);
            }

            String nextNode = processNodeModel.getNextNode();
            if (nextNode != null) {
                String[] nextNodes = nextNode.split(",");
                for (String nodeName : nextNodes) { // next指向的node节点的names，必须是在你的流程定义里定义的其他的node name
                    if (!nodes.containsKey(nodeName)) {
                        throw new IllegalArgumentException("节点[name=" + nodeName + "]不存在");
                    }
                }
            }

            // 如果说你的process node设置了begin=true，就把startNode数量++
            if (processNodeModel.getBegin()) {
                startNode++;
            }
        }

        // 如果说你设置了begin=true的节点数量大于了1，此时不等于1，或者是小于1，没有设置begin节点
        // 这两种情况都是不对的，有且仅有一个begin节点
        if (startNode != 1) {
            throw new IllegalArgumentException("不合法的流程，每个流程只能有一个开始节点");
        }
    }

    public ProcessorDefinition build(ProcessorInstanceCreator creator) throws Exception {
        Map<String, ProcessorNode> processorNodeMap = new HashMap<>();

        ProcessorDefinition processorDefinition = new ProcessorDefinition();
        processorDefinition.setName(name);

        // 第一次循环，将所有的processNode转化为processorNode实例，并保存在集合中，并将第一个节点放入processorDefinition
        for (ProcessNodeModel processNodeModel : nodes.values()) {
            String className = processNodeModel.getClassName();
            // 根据我们的流程节点的class，以及流程节点的name，在这里使用流程节点构造器，去构造流程节点对应的Processor
            // 没有spring容器，就可以把每个节点的class通过反射构建为一个实例对象，然后把实例对象转换为接口类型就可以了
            // 如果有spring容器，一般来说我们都是基于spring环境去跑的，就是直接根据你的class类型，去spring容器里获取这个class类型对应的bean实例
            // 这个bean实例，他就是你的业务代码里自己定义的spring bean，强转为接口类型
            Processor processor = creator.newInstance(className, processNodeModel.getName());
            ProcessorNode processorNode = new ProcessorNode(); // ProcessorNode代表了一个可以运行的流程节点
            processorNode.setProcessor(processor);
            processorNode.setName(processNodeModel.getName());
            if (processNodeModel.getBegin()) { // 如果说你的流程节点数据模型里的begin属性他是true，此时就把你的ProcessorNode设置为
                // ProcessorDefinition里的第一个流程节点
                processorDefinition.setFirst(processorNode);
            }
            // 还会把你的方法调用模式，sync还是async，设置到你的ProcessorNode里去
            processorNode.setInvokeMethod(processNodeModel.getInvokeMethod());
            // 到此为止，就把所有的流程节点，都从流程节点定义数据模型，转换为了ProcessorNode可运行的流程节点
            processorNodeMap.put(processNodeModel.getName(), processorNode);
        }

        // 第二次循环，将所有节点建立关联关系
        // 遍历所有的可运行的ProcessorNode，根据每个ProcessNode他的一个next关系
        // 去进行ProcessorNode之间的依赖关系
        for (ProcessorNode processNode : processorNodeMap.values()) {
            String nextNodeStr = nodes.get(processNode.getName()).getNextNode();
            if (nextNodeStr == null) {
                continue;
            }
            String[] nextNodes = nextNodeStr.split(",");
            for (String nextNode : nextNodes) {
                processNode.addNextNode(processorNodeMap.get(nextNode));
            }
        }

        // 可运行的一个流程就搞好了
        return processorDefinition;
    }
}
